package com.gee.spring.boot.starter.opengemini.core.query;


import com.gee.spring.boot.starter.opengemini.core.domain.ColumnInfo;
import com.gee.spring.boot.starter.opengemini.core.domain.MeasurementInfo;
import com.gee.spring.boot.starter.opengemini.core.domain.SelectFunc;
import com.gee.spring.boot.starter.opengemini.core.domain.lambda.LambdaMeta;
import com.gee.spring.boot.starter.opengemini.core.enums.AndOr;
import com.gee.spring.boot.starter.opengemini.core.enums.AscDesc;
import com.gee.spring.boot.starter.opengemini.core.enums.Condition;
import com.gee.spring.boot.starter.opengemini.core.enums.FillFunc;
import com.gee.spring.boot.starter.opengemini.core.query.segment.*;
import com.gee.spring.boot.starter.opengemini.core.util.ExceptionUtil;
import com.gee.spring.boot.starter.opengemini.core.util.LambdaUtil;
import com.gee.spring.boot.starter.opengemini.core.util.OpengeminiUtil;
import com.gee.spring.boot.starter.opengemini.core.util.PropertyUtil;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.gee.spring.boot.starter.opengemini.core.constant.StrConstant.*;
import static com.gee.spring.boot.starter.opengemini.core.enums.Condition.*;
import static com.gee.spring.boot.starter.opengemini.core.enums.SqlKeyword.*;
import static com.gee.spring.boot.starter.opengemini.core.util.OpengeminiUtil.getMeasurementInfo;


/**
 * @author gepengjun
 * @since 2024/4/28 8:28
 */
public class QueryWrapper<T>{

    private final Class<T> entityClass;

    private final MeasurementInfo measurementInfo;

    private Object[] measurementSuffixFieldValueArr;

    private Object databaseSuffixFieldValue;

    private final SelectSegmentList selectSegmentList = new SelectSegmentList();

    private final ConditionSegmentList conditionSegmentList = new ConditionSegmentList();

    private final GroupBySegmentList groupBySegmentList = new GroupBySegmentList();

    private final OrderBySegmentList orderBySegmentList = new OrderBySegmentList();

    private final LimitOffsetSegment limitOffsetSegment = new LimitOffsetSegment();

    private final FillSegment fillSegment = new FillSegment();

    private Boolean funcTime = false;

    public Class<T> getEntityClass(){
        return entityClass;
    }


    private QueryWrapper(Class<T> tClass){
        this.entityClass = tClass;
        this.measurementInfo = OpengeminiUtil.getMeasurementInfo(tClass);
    }

    public static <T> QueryWrapper<T> newQuery(Class<T> tClass){
        return new QueryWrapper<>(tClass);
    }

    public static <T> QueryWrapper<T> newQuery(Class<T> tClass, Object databaseSuffixFieldValue, Object ... measurementSuffixFieldValueArr){
        QueryWrapper<T> queryWrapper = new QueryWrapper<>(tClass);
        queryWrapper.setMeasurementSuffixFieldValueArr(measurementSuffixFieldValueArr);
        queryWrapper.setDatabaseSuffixFieldValue(databaseSuffixFieldValue);
        return queryWrapper;
    }


    public static <T> QueryWrapper<T> newQueryWithMeasurementSuffix(Class<T> tClass, Object ... measurementSuffixFieldValue){
        QueryWrapper<T> queryWrapper = new QueryWrapper<>(tClass);
        queryWrapper.setMeasurementSuffixFieldValueArr(measurementSuffixFieldValue);
        return queryWrapper;
    }

    public static <T> QueryWrapper<T> newQuery(Class<T> tClass, Object databaseSuffixFieldValue){
        QueryWrapper<T> queryWrapper = new QueryWrapper<>(tClass);
        queryWrapper.setDatabaseSuffixFieldValue(databaseSuffixFieldValue);
        return queryWrapper;
    }

    public void setDatabaseSuffixFieldValue(Object databaseSuffixFieldValue) {
        this.databaseSuffixFieldValue = databaseSuffixFieldValue;
    }

    public void setMeasurementSuffixFieldValueArr(Object[] measurementSuffixFieldValueArr) {
        this.measurementSuffixFieldValueArr = measurementSuffixFieldValueArr;
    }

    public String getDatabase(){
        if (Objects.isNull(databaseSuffixFieldValue)){
            return measurementInfo.getDatabase(null);
        }
        return measurementInfo.getDatabase(null) + UNDERLINE + databaseSuffixFieldValue;
    }

    public QueryWrapper<T> select(String ... columnNames){
        for (String columnName : columnNames) {
            addSelect(new ColumnInfo(null ,columnName, false, EMPTY), null);
        }
        return this;
    }

    public QueryWrapper<T> select(SelectFunc selectFunc, String ... columnNames){
        for (String columnName : columnNames) {
            addSelect(new ColumnInfo(null ,columnName, false, EMPTY), selectFunc);
        }
        return this;
    }

    public QueryWrapper<T> select(SelectFunc selectFunc, String columnName, String funcAlias){
        addSelect(new ColumnInfo(null ,columnName, false, EMPTY), selectFunc);
        return this;
    }

    public QueryWrapper<T> select(SelectFunc selectFunc){
        for (ColumnInfo columnInfo : getMeasurementInfo(entityClass).getFieldColumnInfoMap().values()) {
            if (!columnInfo.getTagFlag()) {
                addSelect(columnInfo, selectFunc);
            }
        }
        funcTime = true;
        return this;
    }

    @SafeVarargs
    public final QueryWrapper<T> select(SelectFunc selectFunc, SFunction<T, ?>... columns){
        for (SFunction<T, ?> column : columns) {
            select(column, selectFunc);
        }
        return this;
    }



    @SafeVarargs
    public final QueryWrapper<T> select(SFunction<T, ?>... columns){
        for (SFunction<T, ?> column : columns) {
            select(column);
        }
        return this;
    }

    public QueryWrapper<T> select(SFunction<T, ?> column){
        return select(column, null);
    }

    public QueryWrapper<T> select(SFunction<T, ?> column, SelectFunc selectFunc){
        addSelect(getColumnInfo(column), selectFunc);
        return this;
    }

    private void addSelect(ColumnInfo columnInfo, SelectFunc selectFunc){
        String column = columnInfo.getColumn();
        if (selectFunc != null){
            if (columnInfo.getTagFlag()){
                throw  ExceptionUtil.re("tag can not with func %s", columnInfo.getField().getName());
            }
            if ("time".equals(column)){
                funcTime = true;
            }
        }
        selectSegmentList.add(new SelectSegment(columnInfo, selectFunc));

    }

    public QueryWrapper<T> eq(SFunction<T, ?> column, Object value){
        return eq(AndOr.AND, column, value);
    }

    public QueryWrapper<T> eq(AndOr andOr, SFunction<T, ?> column, Object value){
        addCondition(andOr, getColumnInfo(column), EQ, value);
        return this;
    }

    public QueryWrapper<T> eq(String column, Object value){
        return eq(column, value, false);
    }


    public QueryWrapper<T> eq(String column, Object value, boolean tag){
        return eq(AndOr.AND, column, value, tag);
    }

    public QueryWrapper<T> eq(AndOr andOr, String column, Object value, boolean tag){
        ColumnInfo columnInfo = new ColumnInfo(null, column, tag, EMPTY);
        addCondition(andOr, columnInfo, EQ, value);
        return this;
    }

    public QueryWrapper<T> ne(String column, Object value){
        return ne(column, value, false);
    }


    public QueryWrapper<T> ne(String column, Object value, boolean tag){
        return ne(AndOr.AND, column, value, tag);
    }

    public QueryWrapper<T> ne(AndOr andOr, String column, Object value, boolean tag){
        ColumnInfo columnInfo = new ColumnInfo(null, column, tag, EMPTY);
        addCondition(andOr, columnInfo, NOT_EQ, value);
        return this;
    }

    public QueryWrapper<T> ne(SFunction<T, ?> column, Object value){
        addCondition(AndOr.AND, getColumnInfo(column), NOT_EQ, value);
        return this;
    }

    public QueryWrapper<T> ne(AndOr andOr, SFunction<T, ?> column, Object value){
        addCondition(andOr, getColumnInfo(column), NOT_EQ, value);
        return this;
    }


    public QueryWrapper<T> lt(SFunction<T, ?> column, Object value){
        return lt(AndOr.AND, column, value);
    }

    public QueryWrapper<T> lt(AndOr andOr, SFunction<T, ?> column, Object value){
        addCondition(andOr, getColumnInfo(column), LT, value);
        return this;
    }

    public QueryWrapper<T> lt(String column, Object value){
        return lt(column, value, false);
    }


    public QueryWrapper<T> lt(String column, Object value, boolean tag){
        return lt(AndOr.AND, column, value, tag);
    }

    public QueryWrapper<T> lt(AndOr andOr, String column, Object value, boolean tag){
        ColumnInfo columnInfo = new ColumnInfo(null, column, tag, EMPTY);
        addCondition(andOr, columnInfo, LT, value);
        return this;
    }

    public QueryWrapper<T> le(SFunction<T, ?> column, Object value){
        return le(AndOr.AND, column, value);
    }

    public QueryWrapper<T> le(AndOr andOr, SFunction<T, ?> column, Object value){
        addCondition(andOr, getColumnInfo(column), LE, value);
        return this;
    }

    public QueryWrapper<T> le(String column, Object value){
        return le(column, value, false);
    }


    public QueryWrapper<T> le(String column, Object value, boolean tag){
        return le(AndOr.AND, column, value, tag);
    }

    public QueryWrapper<T> le(AndOr andOr, String column, Object value, boolean tag){
        ColumnInfo columnInfo = new ColumnInfo(null, column, tag, EMPTY);
        addCondition(andOr, columnInfo, LE, value);
        return this;
    }

    public QueryWrapper<T> gt(SFunction<T, ?> column, Object value){
        return gt(AndOr.AND, column, value);
    }

    public QueryWrapper<T> gt(AndOr andOr, SFunction<T, ?> column, Object value){
        addCondition(andOr, getColumnInfo(column), GT, value);
        return this;
    }

    public QueryWrapper<T> gt(String column, Object value){
        return gt(column, value, false);
    }


    public QueryWrapper<T> gt(String column, Object value, boolean tag){
        return gt(AndOr.AND, column, value, tag);
    }

    public QueryWrapper<T> gt(AndOr andOr, String column, Object value, boolean tag){
        ColumnInfo columnInfo = new ColumnInfo(null, column, tag, EMPTY);
        addCondition(andOr, columnInfo, GT, value);
        return this;
    }

    public QueryWrapper<T> ge(SFunction<T, ?> column, Object value){
        return ge(AndOr.AND, column, value);
    }

    public QueryWrapper<T> ge(AndOr andOr, SFunction<T, ?> column, Object value){
        addCondition(andOr, getColumnInfo(column), GE, value);
        return this;
    }

    public QueryWrapper<T> ge(String column, Object value){
        return ge(column, value, false);
    }


    public QueryWrapper<T> ge(String column, Object value, boolean tag){
        return ge(AndOr.AND, column, value, tag);
    }

    public QueryWrapper<T> ge(AndOr andOr, String column, Object value, boolean tag){
        ColumnInfo columnInfo = new ColumnInfo(null, column, tag, EMPTY);
        addCondition(andOr, columnInfo, GE, value);
        return this;
    }

    public QueryWrapper<T> notNull(SFunction<T, ?> column){
        return notNull(AndOr.AND, column);
    }

    public QueryWrapper<T> notNull(AndOr andOr, SFunction<T, ?> column){
        addCondition(andOr, getColumnInfo(column), NOT_EQ, EMPTY);
        return this;
    }

    public QueryWrapper<T> notNull(String column){
        return notNull(column, false);
    }

    public QueryWrapper<T> notNull(AndOr andOr, String column){
        ColumnInfo columnInfo = new ColumnInfo(null, column, false, EMPTY);
        addCondition(andOr, columnInfo, NOT_EQ, EMPTY);
        return this;
    }

    public QueryWrapper<T> notNull(String column, boolean tag){
        return notNull(AndOr.AND, column, tag);
    }

    public QueryWrapper<T> notNull(AndOr andOr, String column, boolean tag){
        ColumnInfo columnInfo = new ColumnInfo(null, column, tag, EMPTY);
        addCondition(andOr, columnInfo, GE, EMPTY);
        return this;
    }


    private void addCondition(AndOr andOr, ColumnInfo columnInfo, Condition condition, Object value) {
        MeasurementInfo measurementInfo = getMeasurementInfo(entityClass);
        List<String> measurementSuffixFieldList = measurementInfo.getMeasurementSuffixFieldList();
        Field databaseSuffixField = measurementInfo.getDatabaseSuffixField();
        // 表名后缀, 数据库后缀的字段不参与condition
        if (columnInfo.getField() != null){
            if (!CollectionUtils.isEmpty(measurementSuffixFieldList)
                    && measurementSuffixFieldList.contains(columnInfo.getField().getName())){
                return;
            }
            if (databaseSuffixField != null && databaseSuffixField.getName().equals(columnInfo.getField().getName())){
                return;
            }
        }
        conditionSegmentList.add(new ConditionSegment(andOr, columnInfo, condition, value));
    }

    public QueryWrapper<T> groupByTime(Duration duration){
        groupBySegmentList.add(new GroupByTimeSegment("time",duration));
        return this;
    }

    public QueryWrapper<T> groupBy(SFunction<T, ?> column){
        groupBySegmentList.add(new GroupBySegment(getColumnInfo(column).getColumn()));
        return this;
    }

    public QueryWrapper<T> orderByAsc(SFunction<T, ?> column){
        return orderBy(column, AscDesc.ASC);
    }

    public QueryWrapper<T> orderByDesc(SFunction<T, ?> column){
        return orderBy(column, AscDesc.DESC);
    }

    public QueryWrapper<T> orderBy(String column, AscDesc ascDesc){
        orderBySegmentList.add(new OrderBySegment(getColumnInfo(column), ascDesc));
        return this;
    }

    public QueryWrapper<T> orderBy(SFunction<T, ?> column, AscDesc ascDesc){
        orderBySegmentList.add(new OrderBySegment(getColumnInfo(column), ascDesc));
        return this;
    }

    public QueryWrapper<T> limit(Long limit){
        limitOffsetSegment.setLimit(limit);
        return this;
    }

    public QueryWrapper<T> offset(Long offset){
        limitOffsetSegment.setOffset(offset);
        return this;
    }

    public QueryWrapper<T> and(Consumer<QueryWrapper<T>> consumer){
        QueryWrapper<T> newQuery = QueryWrapper.newQuery(entityClass);
        consumer.accept(newQuery);
        ConditionSegmentList childConditionList = newQuery.conditionSegmentList;
        if (! childConditionList.isEmpty()){
            childConditionList.setAndOr(AndOr.AND);
            conditionSegmentList.add(childConditionList);
        }
        return this;
    }

    public QueryWrapper<T> or(Supplier<QueryWrapper<T>> supplier){
        ConditionSegmentList childConditionList = supplier.get().conditionSegmentList;
        childConditionList.setAndOr(AndOr.OR);
        conditionSegmentList.add(childConditionList);
        return this;
    }

    public QueryWrapper<T> fillByFunc(FillFunc fillFunc){
        fillSegment.setFillValue(fillFunc.getKeyWord());
        fillSegment.setByFunc(true);
        return this;
    }

    public QueryWrapper<T> fillByValue(Object value){
        fillSegment.setFillValue(value);
        return this;
    }


    public String toSqlStr(){
        StringBuilder sqlBuilder = new StringBuilder(SELECT.getKeyword()).append(SPACE);
        MeasurementInfo measurementInfo = getMeasurementInfo(this.entityClass);
        if (selectSegmentList.isEmpty()){
            selectSegmentList.addAll(measurementInfo.getFieldColumnInfoMap().values().stream()
                    .map(columnInfo -> new SelectSegment(columnInfo, null))
                    .collect(Collectors.toList())
            );
        }
        String select = selectSegmentList.stream()
                .map(SelectSegment::getSqlSegment)
                .collect(Collectors.joining(COMMA));
        sqlBuilder.append(select).append(SPACE)
                .append(FROM.getKeyword()).append(SPACE).append(measurementInfo.getMeasurementBySuffixValue(measurementSuffixFieldValueArr));

        sqlBuilder.append(SPACE).append(getSqlCommon());

        if (!orderBySegmentList.isEmpty()){
            sqlBuilder.append(SPACE).append(orderBySegmentList.getSqlSegment());
        }
        sqlBuilder.append(SPACE).append(limitOffsetSegment.getSqlSegment());
        return sqlBuilder.toString();
    }

    public String toCountSql(){
        StringBuilder sqlBuilder = new StringBuilder(SELECT.getKeyword()).append(SPACE).append("count(time)");
        MeasurementInfo measurementInfo = getMeasurementInfo(this.entityClass);
        sqlBuilder.append(SPACE)
                .append(FROM.getKeyword()).append(SPACE).append(measurementInfo.getMeasurementBySuffixValue(measurementSuffixFieldValueArr))
                ;
        sqlBuilder.append(getSqlCommon());

        return sqlBuilder.toString();
    }


    private String getSqlCommon(){
        StringBuilder sqlBuilder = new StringBuilder(getConditionSql());
        if (!groupBySegmentList.isEmpty()){
            sqlBuilder.append(SPACE).append(groupBySegmentList.getSqlSegment());
        }
        if (fillSegment.getFillValue() != null){
            sqlBuilder.append(SPACE).append(fillSegment.getSqlSegment());
        }
        return sqlBuilder.toString();
    }

    private String getConditionSql(){
        StringBuilder sqlBuilder = new StringBuilder();
        if (!conditionSegmentList.isEmpty()){
            sqlBuilder.append(SPACE).append(WHERE.getKeyword()).append(SPACE).append(conditionSegmentList.getSqlSegment());
        }
        return sqlBuilder.toString();
    }

    private ColumnInfo getColumnInfo(SFunction<T, ?> column) {
        LambdaMeta meta = LambdaUtil.extract(column);
        String fieldName = PropertyUtil.methodToProperty(meta.getImplMethodName());
        Class<?> instantiatedClass = meta.getInstantiatedClass();
        return getMeasurementInfo(instantiatedClass).getColumnInfoByFieldName(fieldName);
    }

    private ColumnInfo getColumnInfo(String column) {
        return getMeasurementInfo(entityClass).getColumnInfoByColumnName(column);
    }


    public Boolean getFuncTime() {
        return funcTime;
    }
}
